// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <nebula/types/type_holder.h>
#include <nebula/types/data_type.h>
#include <nebula/types/field.h>

namespace nebula {


    /// \brief Base class for all fixed-width data types
    class TURBO_EXPORT FixedWidthType : public DataType {
    public:
        using DataType::DataType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~FixedWidthType() override = default;
    };


    /// \brief Base class for all data types representing primitive values
    class TURBO_EXPORT PrimitiveCType : public FixedWidthType {
    public:
        using FixedWidthType::FixedWidthType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~PrimitiveCType() override = default;
    };

    /// \brief Base class for all numeric data types
    class TURBO_EXPORT NumberType : public PrimitiveCType {
    public:
        using PrimitiveCType::PrimitiveCType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~NumberType() override = default;
    };

    /// \brief Base class for all integral data types
    class TURBO_EXPORT IntegerType : public NumberType {
    public:
        using NumberType::NumberType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~IntegerType() override = default;

        virtual bool is_signed() const = 0;
    };

    /// \brief Base class for all floating-point data types
    class TURBO_EXPORT FloatingPointType : public NumberType {
    public:
        using NumberType::NumberType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~FloatingPointType() override = default;

        enum Precision {
            HALF, SINGLE, FP64
        };

        virtual Precision precision() const = 0;
    };

    /// \brief Base class for all parametric data types
    class ParametricType {
    };

    class TURBO_EXPORT NestedType : public DataType, public ParametricType {
    public:
        using DataType::DataType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~NestedType() override = default;
    };

    namespace detail {

        template<typename DERIVED, typename BASE, Type::type TYPE_ID, typename C_TYPE>
        class TURBO_EXPORT CTypeImpl : public BASE {
        public:
            static constexpr Type::type type_id = TYPE_ID;
            using c_type = C_TYPE;
            using PhysicalType = DERIVED;

            CTypeImpl() : BASE(TYPE_ID) {}

            int bit_width() const override { return static_cast<int>(sizeof(C_TYPE) * CHAR_BIT); }

            DataTypeLayout layout() const override {
                return DataTypeLayout(
                        {DataTypeLayout::Bitmap(), DataTypeLayout::FixedWidth(sizeof(C_TYPE))});
            }

            std::string name() const override { return DERIVED::type_name(); }

            std::string to_string(bool show_metadata = false) const override { return this->name(); }
        };

        template<typename DERIVED, typename BASE, Type::type TYPE_ID, typename C_TYPE>
        constexpr Type::type CTypeImpl<DERIVED, BASE, TYPE_ID, C_TYPE>::type_id;

        template<typename DERIVED, Type::type TYPE_ID, typename C_TYPE>
        class IntegerTypeImpl : public detail::CTypeImpl<DERIVED, IntegerType, TYPE_ID, C_TYPE> {
            bool is_signed() const override { return std::is_signed<C_TYPE>::value; }
        };

    }  // namespace detail



    /// \brief Type class for run-end encoded data
    class TURBO_EXPORT RunEndEncodedType : public NestedType {
    public:
        static constexpr Type::type type_id = Type::RUN_END_ENCODED;

        static constexpr const char *type_name() { return "run_end_encoded"; }

        explicit RunEndEncodedType(std::shared_ptr<DataType> run_end_type,
                                   std::shared_ptr<DataType> value_type);

        ~RunEndEncodedType() override;

        DataTypeLayout layout() const override {
            // A lot of existing code expects at least one buffer
            return DataTypeLayout({DataTypeLayout::AlwaysNull()});
        }

        const std::shared_ptr<DataType> &run_end_type() const { return fields()[0]->type(); }

        const std::shared_ptr<DataType> &get_value_type() const { return fields()[1]->type(); }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "run_end_encoded"; }

        static bool RunEndTypeValid(const DataType &run_end_type);

    private:
        std::string compute_fingerprint() const override;
    };


    std::string type_id_fingerprint(const DataType &type);

}  // namespace nebula
